#include "common_functions.hpp"

zval **get_array_value(HashTable *arr, const char *key, uint key_len, zend_uchar type) {
	zval **val; 
	if (zend_hash_find(arr, key, key_len, (void **)&val) != SUCCESS)
		return NULL; 
	if (!val || Z_TYPE_PP(val) != type)
		return NULL; 
	return val; 
}

static inline bool call_function(const char *name, zval **obj_pp, zval *ret, uint count_args, zval **args) {
	zval *function_name; 
	MAKE_STD_ZVAL(function_name); 
	ZVAL_STRING(function_name, name, 1); 
	return call_user_function(obj_pp ? NULL : EG(function_table), obj_pp, function_name, ret, count_args, args TSRMLS_CC) == SUCCESS; 
}

static inline bool call_function(const char *name, zval **obj_pp, zval *ret, uint count_args, ...) {
	zval *function_name; 
	zval **params = NULL; 
	if (count_args) {
		params = (zval **)calloc(count_args, sizeof(zval *)); 
		va_list vl; 
		va_start(vl, count_args); 
		for (uint i = 0; i < count_args; ++i)
			params[i] = va_arg(vl, zval*); 
		va_end(vl); 
	}
	MAKE_STD_ZVAL(function_name); 
	ZVAL_STRING(function_name, name, 1); 
	bool result = call_user_function(obj_pp ? NULL : EG(function_table), obj_pp, function_name, ret, count_args, params TSRMLS_CC) == SUCCESS; 
	if (params) {
		free(params); 
		params = NULL; 
	}
	return result; 
}

static inline zval *get_class_by_name(const char *name, uint length) {
	zend_class_entry **ce; 
	zval *new_obj; 
	if (!length)
		length = strlen(name); 
	if (zend_lookup_class(name, length, &ce TSRMLS_CC) != SUCCESS)
		return NULL; 
	
	ALLOC_ZVAL(new_obj); 
	object_init_ex(new_obj, *ce); 
	Z_SET_REFCOUNT_P(new_obj, 1); 
	Z_SET_ISREF_P(new_obj); 
	
	return new_obj; 
}

static inline bool call_method(zval *obj, const char *name, uint length, zval **ret, uint param_count, ...) {
	if (!length)
		length = strlen(name); 
	zval **params = NULL; 
	if (param_count) {
		params = (zval **)calloc(param_count, sizeof(zval *)); 
		va_list vl; 
		va_start(vl, param_count); 
		for (uint i = 0; i < param_count; ++i)
			params[i] = va_arg(vl, zval*); 
		va_end(vl); 
	}
	int result = zend_call_method_extended(&obj, Z_OBJCE_P(obj), NULL, name, length, ret, param_count, &params TSRMLS_CC); 
	if (params) {
		free(params); 
		params = NULL; 
	}
	return result == SUCCESS; 
}

/* {{{ zend_call_method
 Only returns the returned zval if retval_ptr != NULL */
ZEND_API int zend_call_method_extended(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval ***params TSRMLS_DC)
{
	int result; 
	zend_fcall_info fci; 
	zval z_fname; 
	zval *retval; 
	HashTable *function_table; 
	
	fci.size = sizeof(fci); 
	/*fci.function_table = NULL; will be read form zend_class_entry of object if needed */
	fci.object_ptr = object_pp ? *object_pp : NULL; 
	fci.function_name = &z_fname; 
	fci.retval_ptr_ptr = retval_ptr_ptr ? retval_ptr_ptr : &retval; 
	fci.param_count = param_count; 
	fci.params = params; 
	fci.no_separation = 1; 
	fci.symbol_table = NULL; 
	
	if (!fn_proxy && !obj_ce) {
		/* no interest in caching and no information already present that is
		 * needed later inside zend_call_function. */
		ZVAL_STRINGL(&z_fname, function_name, function_name_len, 0); 
		fci.function_table = !object_pp ? EG(function_table) : NULL; 
		result = zend_call_function(&fci, NULL TSRMLS_CC); 
	} else {
		zend_fcall_info_cache fcic; 
		fcic.initialized = 1; 
		if (!obj_ce)
			obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; 
		if (obj_ce) {
			function_table = &obj_ce->function_table; 
		} else {
			function_table = EG(function_table); 
		}
		if (!fn_proxy || !*fn_proxy) {
			if (zend_hash_find(function_table, function_name, function_name_len+1, (void **) &fcic.function_handler) == FAILURE) {
				/* error at c-level */
				zend_error(E_CORE_ERROR, "Couldn't find implementation for method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); 
			}
			if (fn_proxy)
				*fn_proxy = fcic.function_handler; 
		} else
			fcic.function_handler = *fn_proxy; 
		fcic.calling_scope = obj_ce; 
		if (object_pp) {
			fcic.called_scope = Z_OBJCE_PP(object_pp); 
		} else if (obj_ce &&
		           !(EG(called_scope) &&
		             instanceof_function(EG(called_scope), obj_ce TSRMLS_CC))) {
			fcic.called_scope = obj_ce; 
		} else
			fcic.called_scope = EG(called_scope); 
		fcic.object_ptr = object_pp ? *object_pp : NULL; 
		result = zend_call_function(&fci, &fcic TSRMLS_CC); 
	}
	
	if (result == FAILURE) {
		/* error at c-level */
		if (!obj_ce)
			obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; 
		if (!EG(exception))
			zend_error(E_CORE_ERROR, "Couldn't execute method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); 
	}
	if (!retval_ptr_ptr)
		if (retval)
			zval_ptr_dtor(&retval);
	return result; 
}
/* }}} */
